#pragma once

#include <iostream>
using namespace std;
#include <time.h>
#include <string>
#include <filesystem>
#include <fstream>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <sstream>

#include "MyMute.hpp"

namespace MyLogModule
{
    using namespace MyMutexModule;
    static const std::string defaultLogPath = "./Log/";
    static const std::string defaulFiletName = "log.txt";

    enum LogGrade
    {
        DEBUG = 1,
        NORMAL,
        WARNING,
        ERROR,
        FATAL
    };

    std::string GradeToString(LogGrade grade)
    {
        switch (grade)
        {
        case DEBUG:
            return "DEBUG";
            break;
        case NORMAL:
            return "NORMAL";
            break;
        case WARNING:
            return "WARNING";
            break;
        case ERROR:
            return "ERROR";
            break;
        case FATAL:
            return "FATAL";
            break;
        default:
            return "UNKNOWN";
            break;
        }
    }

    std::string GetCurTime()
    {
        time_t tm = time(nullptr);
        struct tm cur;
        localtime_r(&tm, &cur);
        char buffer[64];
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 cur.tm_year + 1900,
                 cur.tm_mon + 1,
                 cur.tm_mday,
                 cur.tm_hour,
                 cur.tm_min,
                 cur.tm_sec);
        return buffer;
    }

    // 基类
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void FlushLog(const std::string &message) = 0; // 派生类必须重写
    };

    // 控制台 派生类
    class ConsoleStrategy : public LogStrategy
    {
    public:
        virtual ~ConsoleStrategy() override
        {
        }

        virtual void FlushLog(const std::string &message) override // 派生类重写基类的虚函数
        {
            LockGuard LockGuard(_mutex);
            std::cerr << message << std::endl;
        }

    private:
        Mutex _mutex; // 显示器也是临界资源，保证输出线程的安全
    };

    // 文件 派生类
    class FileStrategy : public LogStrategy
    {
    public:
        FileStrategy(const std::string logPath = defaultLogPath, const std::string logFileName = defaulFiletName)
            : _logPath(logPath), _logFileName(logFileName)
        {
            LockGuard LockGuard(_mutex);
            // filesystem c++17
            if (std::filesystem::exists(_logPath)) // 检测当前路径是否存在
                return;
            try
            {
                std::filesystem::create_directories(_logPath);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << std::endl;
            }
        }
        virtual ~FileStrategy() override
        {
        }

        virtual void FlushLog(const std::string &message) override // 派生类重写基类的虚函数
        {
            LockGuard lockguard(_mutex);
            std::string filename = _logPath + _logFileName;

            // 使用ofstream打开文件filename,以append方式
            std::ofstream out(filename.c_str(), std::ofstream::app);

            if (!out.is_open())
            {
                std::cerr << filename << " 文件打开失败" << std::endl;
                return; // 打开失败
            }

            out << message << "\n"; // 向文件中写
        }

    private:
        std::string _logPath;
        std::string _logFileName;
        Mutex _mutex; // 文件也是临界资源，保证输出线程的安全
    };

    // 具体的日志类
    class Logger
    {
    private:
        std::unique_ptr<LogStrategy> _strategy; // 日志的写入策略
    public:
        Logger()
        {
            UseConsoleStrategy(); // 默认使用控制台策略
        }

        ~Logger()
        {
        }

        void UseConsoleStrategy()
        {
            _strategy = std::make_unique<ConsoleStrategy>();
        }

        void UseFileStrategy()
        {
            _strategy = std::make_unique<FileStrategy>();
        }
        // 内部类
        //[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容（⽀持可变参数）
        class LogMessage
        {
        private:
            std::string _cur_time; // 日志写入时间
            LogGrade _grade;       // 日志等级
            pid_t _pid;            // 进程pid
            std::string _fileName; // 文件名
            int _lineNum;          // 行号
            Logger &_logger;       // 方便进行日志的刷新

            std::string _completeMessage; // 完整的信息
        public:
            LogMessage(LogGrade grade, std::string filename, int lineNum, Logger &logger)
                : _cur_time(GetCurTime()), _grade(grade), _pid(getpid()), _fileName(filename), _lineNum(lineNum), _logger(logger)
            {
                // stringstream不允许拷⻉,所以这⾥就当做格式化功能使⽤
                std::stringstream ssbuffer;
                ssbuffer << "[" << _cur_time << "] "
                         << "[" << GradeToString(_grade) << "] "
                         << "[" << _pid << "] "
                         << "[" << _fileName << "] "
                         << "[" << _lineNum << "] "
                         << " - ";
                _completeMessage = ssbuffer.str(); // 信息格式以初始化完成
            }

            // RAII⻛格，析构的时候进⾏⽇志持久化，采⽤指定的策略
            ~LogMessage()
            {
                if (_logger._strategy)
                {
                    _logger._strategy->FlushLog(_completeMessage);
                }
            }

            // 为支持连续的 LogMessage << "11" << "222";
            template <class T>
            LogMessage &operator<<(const T &message)
            {
                stringstream ssbuffer;
                ssbuffer << message;
                _completeMessage += ssbuffer.str(); // 每次将输入信息添加到日子信息后
                return *this;
            }
        }; // class LogMessage

        // 故意拷⻉，形成LogMessage临时对象，临时对象内包含独⽴⽇志数据,后续在被<<时，会被持续引⽤，
        // 直到完成输⼊,才会⾃动析构临时LogMessage，⾄此也完成了⽇志的显⽰或者刷新

        // 未来采⽤宏替换，进⾏⽂件名和代码⾏数的获取
    public:
        LogMessage operator()(LogGrade grade, const std::string &filename, int lineNum)
        {
            LogMessage tmp(grade, filename, lineNum, *this);
            return tmp;
            //传值返回，若没有对象接收，拷贝构造会被编译器优化掉！！！！！！！
        }

    }; // class Logger

    // 定义全局的Logger 对象
    Logger logger;

// 使⽤宏，可以进⾏代码插⼊，⽅便随时获取⽂件名和⾏号
#define LOG(grade) logger(grade, __FILE__, __LINE__)

// 提供选择使⽤何种⽇志策略的⽅法
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy()
#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileStrategy()

} // namespace MyLogModule
